package com.wolfking.converter.service;

import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import com.wolfking.converter.config.DynamicDataSourceContext;
import com.wolfking.converter.entity.ExecuteResult;
import com.wolfking.converter.enums.ConverterType;
import com.wolfking.converter.rowmapper.AbstractCamelRowMapper;
import com.wolfking.converter.rowmapper.DefaultFieldRowMapper;
import com.wolfking.converter.rowmapper.DefaultJsonRowMapper;
import com.wolfking.converter.transform.FieldTransform;
import com.wolfking.converter.util.JsonConverterUtil;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.jdbc.core.JdbcOperations;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.jdbc.support.rowset.SqlRowSet;
import org.springframework.jdbc.support.rowset.SqlRowSetMetaData;
import org.springframework.stereotype.Component;

import javax.sql.DataSource;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Proxy;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Map;
import java.util.Objects;

@Slf4j
@Getter
@Component
public class JdbcTemplateService implements InitializingBean
//        , CommandLineRunner
{

    private JdbcOperations jdbcTemplate;

    private NamedParameterJdbcOperations namedParameterJdbcTemplate;

    @Autowired
    private DynamicDataSourceContext dataSourceContext;

    @Autowired
    private DataSource dataSource;

    @Override
    public void afterPropertiesSet() {
        //用动态代理对两个Template做代码增强，执行后删除ThreadLocal的数据源绑定
        JdbcTemplate a = new JdbcTemplate(dataSource);
        NamedParameterJdbcTemplate b = new NamedParameterJdbcTemplate(dataSource);
        this.jdbcTemplate = (JdbcOperations) Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), new Class[]{JdbcOperations.class}, (proxy, method, args) -> {
            if (method.getDeclaringClass().equals(JdbcOperations.class)) {
                try {
                    return method.invoke(a, args);
                } catch (InvocationTargetException e) {
                    log.warn("InvocationTargetException");
                    throw e.getCause() != null ? e.getCause() : e;
                } finally {
                    dataSourceContext.clearDataSourceType();
                }
            } else {
                return method.invoke(a, args);
            }
        });

        this.namedParameterJdbcTemplate = (NamedParameterJdbcOperations) Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), new Class[]{NamedParameterJdbcOperations.class}, (proxy, method, args) -> {
            if (method.getDeclaringClass().equals(NamedParameterJdbcOperations.class)
                    && !method.getName().equalsIgnoreCase("getJdbcOperations")) {
                try {
                    return method.invoke(b, args);
                } catch (InvocationTargetException e) {
                    log.warn("InvocationTargetException");
                    throw e.getCause() != null ? e.getCause() : e;
                } finally {
                    dataSourceContext.clearDataSourceType();
                }
            } else {
                return method.invoke(b, args);
            }
        });

        log.info("init success");
    }


    public <T> T queryOne(String dataSource, String sql, Class<T> clazz, Object... args) {
        return queryOne(dataSource, sql, clazz, ConverterType.FIELD, args);
    }


    public <T> T queryOne(String dataSource, String sql, Class<T> clazz, Map<String, FieldTransform<?>> fieldTransformMap, Object... args) {
        return queryOne(dataSource, sql, clazz, fieldTransformMap, ConverterType.FIELD, args);
    }


    public <T> T queryOne(String dataSource, String sql, Class<T> clazz, ConverterType converterType, Object... args) {
        return queryOne(dataSource, sql, clazz, Maps.newHashMap(), converterType, args);
    }

    public <T> T queryOne(String dataSource, String sql, Class<T> clazz, Map<String, FieldTransform<?>> fieldTransformMap, ConverterType converterType, Object... args) {
        dataSourceContext.setDataSourceType(dataSource);
        List<T> query = converterType == ConverterType.FIELD ?
                jdbcTemplate.query(sql, new DefaultFieldRowMapper<>(clazz, fieldTransformMap), args) :
                jdbcTemplate.query(sql, new DefaultJsonRowMapper<>(clazz, fieldTransformMap), args);
        if (query.size() > 0) {
            return query.get(0);
        }
        return null;
    }

    public <T> T queryOne(String dataSource, String sql, Class<T> clazz, Map<String, FieldTransform<?>> fieldTransformMap, ConverterType converterType, Map<String, Object> param) {
        dataSourceContext.setDataSourceType(dataSource);
        List<T> query = converterType == ConverterType.FIELD ?
                namedParameterJdbcTemplate.query(sql, param, new DefaultFieldRowMapper<>(clazz, fieldTransformMap)) :
                namedParameterJdbcTemplate.query(sql, param, new DefaultJsonRowMapper<>(clazz, fieldTransformMap));
        if (query.size() > 0) {
            return query.get(0);
        }
        return null;
    }


    public <T> List<T> queryList(String dataSource, String sql, Class<T> clazz, Object... args) {
        return queryList(dataSource, sql, clazz, ConverterType.FIELD, args);
    }

    public <T> List<T> queryList(String dataSource, String sql, Class<T> clazz, Map<String, FieldTransform<?>> fieldTransformMap, Object... args) {
        return queryList(dataSource, sql, clazz, fieldTransformMap, ConverterType.FIELD, args);
    }


    public <T> List<T> queryList(String dataSource, String sql, Class<T> clazz, ConverterType converterType, Object... args) {
        return queryList(dataSource, sql, clazz, Maps.newHashMap(), converterType, args);
    }

    public <T> List<T> queryList(String dataSource, String sql, Class<T> clazz, Map<String, FieldTransform<?>> fieldTransformMap, ConverterType converterType, Object... args) {
        dataSourceContext.setDataSourceType(dataSource);
        return converterType == ConverterType.FIELD ?
                jdbcTemplate.query(sql, new DefaultFieldRowMapper<>(clazz, fieldTransformMap), args) :
                jdbcTemplate.query(sql, new DefaultJsonRowMapper<>(clazz, fieldTransformMap), args);
    }


    public <T> List<T> queryList(String dataSource, String sql, Class<T> clazz, Map<String, FieldTransform<?>> fieldTransformMap, ConverterType converterType, Map<String, Object> params) {
        dataSourceContext.setDataSourceType(dataSource);
        return converterType == ConverterType.FIELD ?
                namedParameterJdbcTemplate.query(sql, params, new DefaultFieldRowMapper<>(clazz, fieldTransformMap)) :
                namedParameterJdbcTemplate.query(sql, params, new DefaultJsonRowMapper<>(clazz, fieldTransformMap));
    }

    public int queryCount(String dataSource, String sql, Map<String, Object> params) {
        dataSourceContext.setDataSourceType(dataSource);
        Integer integer = namedParameterJdbcTemplate.queryForObject(sql, params, Integer.class);
        return Objects.isNull(integer) ? 0 : integer;
    }

    public JsonObject queryObject(String dataSource, String sql, Object... args) {
        return queryObject(dataSource, sql, Maps.newHashMap(), args);
    }

    public JsonObject queryObject(String dataSource, String sql, Map<String, FieldTransform<?>> fieldTransformMap, Object... args) {
        dataSourceContext.setDataSourceType(dataSource);
        Map<String, Object> objectMap = jdbcTemplate.queryForMap(sql, args);
        return JsonConverterUtil.assemblyObject(objectMap, fieldTransformMap);
    }

    public JsonObject queryObject(String dataSource, String sql, Map<String, Object> params) {
        return queryObject(dataSource, sql, Maps.newHashMap(), params);
    }

    public JsonObject queryObject(String dataSource, String sql, Map<String, FieldTransform<?>> fieldTransformMap, Map<String, Object> params) {
        try {
            dataSourceContext.setDataSourceType(dataSource);
            Map<String, Object> objectMap = namedParameterJdbcTemplate.queryForMap(sql, params);
            return JsonConverterUtil.assemblyObject(objectMap, fieldTransformMap);
        } catch (EmptyResultDataAccessException e) {
            return null;
        }
    }

    public JsonObject queryObjectUnderLineToCamel(String dataSource, String sql, Map<String, FieldTransform<?>> fieldTransformMap, Map<String, Object> params) {
        try {
            dataSourceContext.setDataSourceType(dataSource);
            Map<String, Object> objectMap = namedParameterJdbcTemplate.queryForMap(sql, params);
            Map<String, Object> newMap = assemblyNewMap(objectMap, fieldTransformMap);
            return JsonConverterUtil.assemblyObject(newMap, fieldTransformMap);
        } catch (EmptyResultDataAccessException e) {
            return null;
        }
    }

    public JsonArray queryArray(String dataSource, String sql, Object... args) {
        return queryArray(dataSource, sql, Maps.newHashMap(), args);
    }

    public JsonArray queryArray(String dataSource, String sql, Map<String, FieldTransform<?>> fieldTransformMap, Object... args) {
        dataSourceContext.setDataSourceType(dataSource);
        List<Map<String, Object>> maps = jdbcTemplate.queryForList(sql, args);
        return JsonConverterUtil.assemblyObjectArray(maps, fieldTransformMap);
    }


    public JsonArray queryArray(String dataSource, String sql, Map<String, Object> params) {
        return queryArray(dataSource, sql, Maps.newHashMap(), params);
    }

    public JsonArray queryArray(String dataSource, String sql, Map<String, FieldTransform<?>> fieldTransformMap, Map<String, Object> params) {
        dataSourceContext.setDataSourceType(dataSource);
        List<Map<String, Object>> maps = namedParameterJdbcTemplate.queryForList(sql, params);
        return JsonConverterUtil.assemblyObjectArray(maps, fieldTransformMap);
    }


    public ExecuteResult queryArray2ExecuteResult(String dataSource, String sql, Map<String, FieldTransform<?>> fieldTransformMap, Map<String, Object> params) {
        List<String> columnNames = Lists.newArrayList();
        List<Map<String, Object>> maps = Lists.newArrayList();
        dataSourceContext.setDataSourceType(dataSource);
        SqlRowSet sqlRowSet = namedParameterJdbcTemplate.queryForRowSet(sql, params);
        SqlRowSetMetaData metaData = sqlRowSet.getMetaData();
        for (int i = 1; i <= metaData.getColumnCount(); i++) {
            columnNames.add(metaData.getColumnName(i));
        }
        while (sqlRowSet.next()) {
            Map<String, Object> oneRow = Maps.newHashMap();
            for (String columnName : columnNames) {
                oneRow.put(columnName, sqlRowSet.getObject(columnName));
            }
            maps.add(oneRow);
        }
        ExecuteResult executeResult = new ExecuteResult();
        executeResult.setColumnNames(columnNames);
        executeResult.setArray(JsonConverterUtil.assemblyObjectArray(maps, fieldTransformMap));
        return executeResult;
    }

    public JsonArray queryArrayUnderLineToCamel(String dataSource, String sql, Map<String, FieldTransform<?>> fieldTransformMap, Map<String, Object> params) {
        dataSourceContext.setDataSourceType(dataSource);
        List<Map<String, Object>> maps = namedParameterJdbcTemplate.queryForList(sql, params);
        List<Map<String, Object>> newMaps = Lists.newArrayList();
        for (Map<String, Object> map : maps) {
            newMaps.add(assemblyNewMap(map, fieldTransformMap));
        }
        return JsonConverterUtil.assemblyObjectArray(newMaps, fieldTransformMap);
    }


    public ExecuteResult queryArrayUnderLineToCamel2ExecuteResult(String dataSource, String sql, Map<String, FieldTransform<?>> fieldTransformMap, Map<String, Object> params) {
        List<String> columnNames = Lists.newArrayList();
        List<Map<String, Object>> maps = Lists.newArrayList();
        dataSourceContext.setDataSourceType(dataSource);
        SqlRowSet sqlRowSet = namedParameterJdbcTemplate.queryForRowSet(sql, params);
        SqlRowSetMetaData metaData = sqlRowSet.getMetaData();
        for (int i = 1; i <= metaData.getColumnCount(); i++) {
            columnNames.add(metaData.getColumnName(i));
        }
        while (sqlRowSet.next()) {
            Map<String, Object> oneRow = Maps.newHashMap();
            for (String columnName : columnNames) {
                Object object = sqlRowSet.getObject(columnName);
                if (object instanceof byte[]) {
                    oneRow.put(columnName, new String((byte[]) object, StandardCharsets.UTF_8));
                } else {
                    oneRow.put(columnName, object);
                }
            }
            maps.add(oneRow);
        }
        List<Map<String, Object>> newMaps = Lists.newArrayList();
        for (Map<String, Object> map : maps) {
            newMaps.add(assemblyNewMap(map, fieldTransformMap));
        }
        for (int i = 0; i < columnNames.size(); i++) {
            String column = columnNames.get(i);
            String newColumn = AbstractCamelRowMapper.lineToHump(column);
            if (!column.equals(newColumn)) {
                if (!columnNames.contains(newColumn)) {
                    columnNames.set(i, newColumn);
                }
            }
        }
        ExecuteResult executeResult = new ExecuteResult();
        executeResult.setColumnNames(columnNames);
        executeResult.setArray(JsonConverterUtil.assemblyObjectArray(newMaps, fieldTransformMap));
        return executeResult;
    }


    private Map<String, Object> assemblyNewMap(Map<String, Object> map, Map<String, FieldTransform<?>> fieldTransformMap) {
        Map<String, Object> newMap = Maps.newHashMap();
        map.forEach((k, v) -> {
            String lineToHump = AbstractCamelRowMapper.lineToHump(k);
            if (lineToHump.equals(k)) {
                newMap.put(k, v);
            } else if (!map.containsKey(lineToHump)) {
                FieldTransform<?> transform = fieldTransformMap.remove(k);
                if (transform != null && !fieldTransformMap.containsKey(lineToHump)) {
                    fieldTransformMap.put(lineToHump, transform);
                }
                newMap.put(lineToHump, v);
            }
        });
        return newMap;
    }
}
